<?php
/**
 * Created by PhpStorm.
 * User: lee
 * Date: 2018/11/21
 * Time: 10:52 AM
 */

namespace Biz\module\Decoupling;

use Biz\model\eventlog\TransactionLog;
use Doraemon\tools\HhzClient;
use Doraemon\tools\HhzEnv;
use Doraemon\tools\Log as tLog;
use Doraemon\amqp\Queue as aQueue;
use Doraemon\amqp\Queue;


abstract class AbstractMain
{
    const NAME = '';
    protected $data = [];
    private $maxRetries = 100;

    abstract public function main($message, $data);


    public function __construct($message, $queueName)
    {

        $data = $this->buildData($message, $queueName);

        $this->execWorker($message, $data);

    }


    protected function appendLog($msg, $data = [], $evel = 'info')
    {
        $logMethod = "\\" . tLog::class . "::event_" . $this->data['class_short_name'] . "_" . $evel;
        $msg = static::NAME . " " . $msg;

        $logMethod($msg, $data);
    }

    protected function appendData($data = [])
    {
        $this->data = array_merge($this->data, $data);
    }


    private function buildData($message, $queueName)
    {
        $data = json_decode($message->body, true);

        $data['header']['queue_name'] = $queueName;


        //解析类名
        $reflect = new \ReflectionClass(static::class);

        $className = explode('\\', $reflect->getName());
        $classNameLen = count($className);
        $className = $className[$classNameLen - 2] . '-' . $className[$classNameLen - 1];

        $this->appendData(['class_name' => $className]);
        $this->appendData(['class_short_name' => str_replace('-', '', $className)]);


        //构建业务ID
        $suffix = $this->data['class_name'] . '-' . $data['header']['msg_id'];
        $bizId = TransactionLog::genBizId(TransactionLog::BIZ_PREFIX_EVENT, $suffix);
        $this->appendData(['bizid' => $bizId]);

        //消息体
        $this->appendData(['msg_body' => $message->body]);

        //解析后的消息体
        $this->appendData(['msg_parse_body' => $data]);


        return $data;
    }


    private function initExecSlot($execMethods)
    {

        $slotInfo = TransactionLog::getSlotInfo($this->data['bizid'], 'main');

        if (!empty($slotInfo)) {
            return true;
        }

        $ret = TransactionLog::initExecSlot($this->data['bizid'], $execMethods, '', 'init:data_init');

        return $ret;


    }

    private function execWorker($message, $data)
    {
        try {
            $this->main($message, $data);

            $this->execChildMethods();

        } catch (\Exception $e) {

            $this->catchException($e, $data);

            throw $e;

        }


    }

    private function execChildMethods()
    {
        $execMethods = get_class_methods(static::class);

        $execMethods = array_filter($execMethods, function ($v) {
            if (strpos($v, '_exec_') === 0) {
                return true;
            }
            return false;
        });
        $execMethods = array_values($execMethods);

        if (empty($execMethods)) {
            return false;
        }

        $this->appendLog('开始任务');

        //创建幂等执行槽
        $this->initExecSlot($execMethods);

        //解析执行优先顺序和依赖
        $this->parseExecOrder($execMethods);

        //开始执行槽里的任务
        foreach ($execMethods as $methods) {
            $this->startTrace($methods['slot']);
            $this->execMethod($methods);
            $this->stopTrace();
        }

        //判断是否执行成功 ，失败则抛异常进行消息拒绝，下一次重新尝试执行
        $this->judgeExecResult($execMethods);

        $this->appendLog('执行完毕');

    }

    private function parseExecOrder(&$execMethods)
    {
        $execMethods = array_map(function ($v) {

            preg_match('/([\w\_]+)_(after)/', $v, $matches);
            $data = [
                'slot' => $v,
                'parent' => '',
                'op_type' => 'owner'
            ];
            if (isset($matches[1])) {
                $data['parent'] = $matches[1];
            }

            if (isset($matches[2])) {
                $data['op_type'] = $matches[2];
            }

            return $data;

        }, $execMethods);
    }


    private function execMethod($methods)
    {
        try {
            $slotInfo = TransactionLog::getSlotInfo($this->data['bizid'], $methods['slot']);

            if (!empty($slotInfo) && $slotInfo['status'] != TransactionLog::STATUS_NORMAL) {
                return true;
            }
            if ($slotInfo['retries'] > $this->maxRetries) {

                TransactionLog::execSlotSysPause($this->data['bizid'], $methods['slot']);

                return true;
            }

            TransactionLog::execSlotRetryMark($this->data['bizid'], $methods['slot']);

            if ($methods['op_type'] == 'after') {
                $beforeSlotInfo = TransactionLog::getSlotInfo($this->data['bizid'], $methods['parent']);

                if (!empty($beforeSlotInfo)) {
                    if ($beforeSlotInfo['status'] == TransactionLog::STATUS_NORMAL) {

                        TransactionLog::execSlotPauseMark($this->data['bizid'], $methods['slot'], "wait:" . $methods['parent']);

                        return false;
                    }
                }
            }


            TransactionLog::action(function () use ($methods) {
                $result = TransactionLog::execSlotOnlyOnce($this->data['bizid'], $methods['slot']);

                if ($result === false) {
                    return false;
                }
                $result = $this->{$methods['slot']}();

                if ($result === false) {
                    return false;
                }
                return true;
            });


        } catch (\Exception $e) {
            //如果是线上则标注异常日志
            if (HhzEnv::isProduction() || HhzEnv::isProductionWithoutApi2() || HhzEnv::isProductionWithoutGray()) {
                TransactionLog::execSlotExceptionMark($this->data['bizid'], $methods['slot'],
                    vsprintf("exception:e_msg=%s,e_file=%s,e_line=%s", [
                        $e->getMessage(),
                        $e->getFile(),
                        $e->getLine()
                    ]));

                $this->catchException($e, $this->data);
            }
        }
    }


    private function catchException($e, $data = [])
    {
        tLog::event_consume_info(static::NAME . '执行失败', [
            'data' => $data,
            'e_msg' => $e->getMessage(),
            'e_file' => $e->getFile(),
            'e_line' => $e->getLine()
        ]);


    }

    private function judgeExecResult($execMethods)
    {
        $list = TransactionLog::getSysPauseSlotInfos($this->data['bizid']);

        if (count($list) > 0) {
            //事件消费任务出现异常
            Queue::insertQueue("queue/Wechatnotice/toTag", [
                "totag" => \Biz\pockets\constInterface\BizWechatTagConfig::TAG_BIZ_RD_SYSTEM,
                "content" => vsprintf('任务bizid=%s,有因异常重复修复次数过多而被暂停的slot，请手动消除异常', [
                    $this->data['bizid'],
                ])
            ], Queue::QUEUE_BIZ);

            return true;
        }

        $slotInfo = TransactionLog::getSlotInfo($this->data['bizid'],'main');

        if(($slotInfo['mcounter'] - $slotInfo['acounter']) > 0) {

            throw new \Exception(vsprintf('%s 当前有 %s 个任务未执行完毕，任务开始时间:%s', [
                $this->data['bizid'],
                $slotInfo['mcounter'] - $slotInfo['acounter'],
                $slotInfo['addtime']
            ]));
        }



        TransactionLog::execMainSlotSuccess($this->data['bizid']);
    }

    private function genXdebugLogName($filename)
    {
        $base = '/data/logs_bak/xdebug/';
        $logPath = sprintf('%s%s/%s', $base, date('Ymd'), $this->data['bizid']);
        if (!file_exists($logPath)) {
            mkdir($logPath, 0777, true);
        }
        return $logPath . '/' . $filename;
    }

    private function startTrace($slot)
    {
        if (function_exists('xdebug_start_trace')) {
            $fileName = $this->genXdebugLogName($slot);
            xdebug_start_trace($fileName);
        }

    }

    private function stopTrace()
    {
        if (function_exists('xdebug_stop_trace')) {
            xdebug_stop_trace();
        }

    }


}